/*
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * Copyright (c) 1999-2008 Apple Inc.  All Rights Reserved.
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_LICENSE_HEADER_END@
 *
 */
 /*
	 Copyleft (c) 2012-2016 EasyDarwin.ORG.  All rights reserved.
	 Github: https://github.com/EasyDarwin
	 WEChat: EasyDarwin
	 Website: http://www.EasyDarwin.org
 */

#include <sys/stat.h>

#ifndef __Win32__
#include <unistd.h>
#endif

#include "XMLParser.h"

XMLParser::XMLParser(char* inPath, DTDVerifier* verifier)
	: fRootTag(nullptr), fFilePath(nullptr)
{
	StrPtrLen thePath(inPath);
	fFilePath = thePath.GetAsCString();
	fFile.Set(inPath);
	fVerifier = verifier;
}

XMLParser::~XMLParser()
{
	if (fRootTag)
		delete fRootTag;

	delete[] fFilePath;
}

bool XMLParser::ParseFile(char* errorBuffer, int errorBufferSize)
{
	if (fRootTag != nullptr)
	{
		delete fRootTag;    // flush old data
		fRootTag = nullptr;
	}

	fFile.Set(fFilePath);

	if (errorBufferSize < 500) errorBuffer = nullptr;  // Just a hack to avoid checking everywhere
	if ((fFile.GetLength() == 0) || fFile.IsDir())
	{
		if (errorBuffer != nullptr)
			qtss_snprintf(errorBuffer, errorBufferSize, "Couldn't read xml file");
		return false;   // we don't have a valid file;
	}

	char* fileData = new char[(SInt32)(fFile.GetLength() + 1)];
	UInt32 theLengthRead = 0;
	fFile.Read(0, fileData, (UInt32)fFile.GetLength(), &theLengthRead);

	StrPtrLen theDataPtr(fileData, theLengthRead);
	StringParser theParser(&theDataPtr);

	fRootTag = new XMLTag();
	bool result = fRootTag->ParseTag(&theParser, fVerifier, errorBuffer, errorBufferSize);
	if (!result)
	{
		// got error parsing file
		delete fRootTag;
		fRootTag = nullptr;
	}

	delete[]fileData;

	fFile.Close();

	return result;
}

bool  XMLParser::DoesFileExist()
{
	bool itExists = false;
	fFile.Set(fFilePath);
	if ((fFile.GetLength() > 0) && (!fFile.IsDir()))
		itExists = true;
	fFile.Close();

	return itExists;
}

bool  XMLParser::DoesFileExistAsDirectory()
{
	bool itExists = false;
	fFile.Set(fFilePath);
	if (fFile.IsDir())
		itExists = true;
	fFile.Close();

	return itExists;
}

bool  XMLParser::CanWriteFile()
{
	//
	// First check if it exists for reading
	FILE* theFile = ::fopen(fFilePath, "r");
	if (theFile == nullptr)
		return true;

	::fclose(theFile);

	//
	// File exists for reading, check if we can write it
	theFile = ::fopen(fFilePath, "a");
	if (theFile == nullptr)
		return false;

	//
	// We can read and write
	::fclose(theFile);
	return true;
}

void XMLParser::SetRootTag(XMLTag* tag)
{
	if (fRootTag != nullptr)
		delete fRootTag;
	fRootTag = tag;
}

void XMLParser::WriteToFile(char** fileHeader)
{
	char theBuffer[8192];
	ResizeableStringFormatter formatter(theBuffer, 8192);

	//
	// Write the file header
	for (UInt32 a = 0; fileHeader[a] != nullptr; a++)
	{
		formatter.Put(fileHeader[a]);
		formatter.Put(kEOLString);
	}

	if (fRootTag)
		fRootTag->FormatData(&formatter, 0);

	//
	// New libC code. This seems to work better on Win32
	formatter.PutTerminator();
	FILE* theFile = ::fopen(fFilePath, "w");
	if (theFile == nullptr)
		return;

	qtss_fprintf(theFile, "%s", formatter.GetBufPtr());
	::fclose(theFile);

#if __MacOSX__
	(void) ::chown(fFilePath, 76, 80);//owner qtss, group admin
#endif

#ifndef __Win32__
	::chmod(fFilePath, S_IRUSR | S_IWUSR | S_IRGRP);
#endif
}

UInt8 XMLTag::sNonNameMask[] =
{
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //0-9 
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //10-19 
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //20-29
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //30-39 
	1, 1, 1, 1, 1, 0, 0, 1, 0, 0, //40-49 '.' and '-' are name chars
	0, 0, 0, 0, 0, 0, 0, 0, 0, 1, //50-59 ':' is a name char
	1, 1, 1, 1, 1, 0, 0, 0, 0, 0, //60-69 //stop on every character except a letter or number
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //70-79
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //80-89
	0, 1, 1, 1, 1, 0, 1, 0, 0, 0, //90-99 '_' is a name char
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //100-109
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //110-119
	0, 0, 0, 1, 1, 1, 1, 1, 1, 1, //120-129
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //130-139
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //140-149
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //150-159
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //160-169
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //170-179
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //180-189
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //190-199
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //200-209
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //210-219
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //220-229
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //230-239
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //240-249
	1, 1, 1, 1, 1, 1             //250-255
};

XMLTag::XMLTag() :
	fTag(nullptr),
	fValue(nullptr),
	fElem(nullptr)
{
	fElem = this;
}

XMLTag::XMLTag(char* tagName) :
	fTag(nullptr),
	fValue(nullptr),
	fElem(nullptr)
{
	fElem = this;
	StrPtrLen temp(tagName);
	fTag = temp.GetAsCString();
}

XMLTag::~XMLTag()
{
	if (fTag)
		delete fTag;
	if (fValue)
		delete fValue;

	OSQueueElem* elem;
	while ((elem = fAttributes.DeQueue()) != nullptr)
	{
		XMLAttribute* attr = (XMLAttribute*)elem->GetEnclosingObject();
		delete attr;
	}

	while ((elem = fEmbeddedTags.DeQueue()) != nullptr)
	{
		XMLTag* tag = (XMLTag*)elem->GetEnclosingObject();
		delete tag;
	}

	if (fElem.IsMemberOfAnyQueue())
		fElem.InQueue()->Remove(&fElem);    // remove from parent tag
}

void XMLTag::consumeIfComment(StringParser* parser)
{
	if ((parser->GetDataRemaining() > 2) && ((*parser)[1] == '-') && ((*parser)[2] == '-'))
	{
		// this is a comment, so skip to end of comment
		parser->ConsumeLength(nullptr, 2); // skip '--'

		// look for -->
		while ((parser->GetDataRemaining() > 2) && ((parser->PeekFast() != '-') ||
			((*parser)[1] != '-') || ((*parser)[2] != '>')))
		{
			if (parser->PeekFast() == '-') parser->ConsumeLength(nullptr, 1);
			parser->ConsumeUntil(nullptr, '-');
		}

		if (parser->GetDataRemaining() > 2) parser->ConsumeLength(nullptr, 3); // consume -->
	}
}

bool XMLTag::ParseTag(StringParser* parser, DTDVerifier* verifier, char* errorBuffer, int errorBufferSize)
{
	while (true)
	{
		if (!parser->GetThru(nullptr, '<'))
		{
			if (errorBuffer != nullptr)
				qtss_snprintf(errorBuffer, errorBufferSize, "Couldn't find a valid tag");
			return false;   // couldn't find beginning of tag
		}

		char c = parser->PeekFast();
		if (c == '/')
		{
			if (errorBuffer != nullptr)
				qtss_snprintf(errorBuffer, errorBufferSize, "End tag with no begin tag on line %d", parser->GetCurrentLineNumber());
			return false;   // we shouldn't be seeing a close tag here
		}

		if ((c != '!') && (c != '?'))
			break;  // this should be the beginning of a regular tag

		consumeIfComment(parser);
		// otherwise this is a processing instruction or a c-data, so look for the next tag
	}

	int tagStartLine = parser->GetCurrentLineNumber();

	StrPtrLen temp;
	parser->ConsumeUntil(&temp, sNonNameMask);
	if (temp.Len == 0)
	{
		if (errorBuffer != nullptr)
		{
			if (parser->GetDataRemaining() == 0)
				qtss_snprintf(errorBuffer, errorBufferSize, "Unexpected end of file on line %d", parser->GetCurrentLineNumber());
			else
				qtss_snprintf(errorBuffer, errorBufferSize, "Unexpected character (%c) on line %d", parser->PeekFast(), parser->GetCurrentLineNumber());
		}
		return false;   // bad file
	}

	fTag = temp.GetAsCString();

	parser->ConsumeWhitespace();
	while ((parser->PeekFast() != '>') && (parser->PeekFast() != '/'))
	{
		// we must have an attribute value for this tag
		XMLAttribute* attr = new XMLAttribute;
		fAttributes.EnQueue(&attr->fElem);
		parser->ConsumeUntil(&temp, sNonNameMask);
		if (temp.Len == 0)
		{
			if (errorBuffer != nullptr)
			{
				if (parser->GetDataRemaining() == 0)
					qtss_snprintf(errorBuffer, errorBufferSize, "Unexpected end of file on line %d", parser->GetCurrentLineNumber());
				else
					qtss_snprintf(errorBuffer, errorBufferSize, "Unexpected character (%c) on line %d", parser->PeekFast(), parser->GetCurrentLineNumber());
			}
			return false;   // bad file
		}

		attr->fAttrName = temp.GetAsCString();

		if (!parser->Expect('='))
		{
			if (errorBuffer != nullptr)
				qtss_snprintf(errorBuffer, errorBufferSize, "Missing '=' after attribute %s on line %d", attr->fAttrName, parser->GetCurrentLineNumber());
			return false;   // bad attribute specification
		}
		if (!parser->Expect('"'))
		{
			if (errorBuffer != nullptr)
				qtss_snprintf(errorBuffer, errorBufferSize, "Attribute %s value not in quotes on line %d", attr->fAttrName, parser->GetCurrentLineNumber());
			return false;   // bad attribute specification
		}

		parser->ConsumeUntil(&temp, '"');
		attr->fAttrValue = temp.GetAsCString();
		if (!parser->Expect('"'))
		{
			if (errorBuffer != nullptr)
				qtss_snprintf(errorBuffer, errorBufferSize, "Attribute %s value not in quotes on line %d", attr->fAttrName, parser->GetCurrentLineNumber());
			return false;   // bad attribute specification
		}

		if (verifier && !verifier->IsValidAttributeName(fTag, attr->fAttrName))
		{
			if (errorBuffer != nullptr)
				qtss_snprintf(errorBuffer, errorBufferSize, "Attribute %s not allowed in tag %s on line %d", attr->fAttrName, fTag, parser->GetCurrentLineNumber());
			return false;   // bad attribute specification
		}

		if (verifier && !verifier->IsValidAttributeValue(fTag, attr->fAttrName, attr->fAttrValue))
		{
			if (errorBuffer != nullptr)
				qtss_snprintf(errorBuffer, errorBufferSize, "Bad value for attribute %s on line %d", attr->fAttrName, parser->GetCurrentLineNumber());
			return false;   // bad attribute specification
		}

		parser->ConsumeWhitespace();
	}

	if (parser->PeekFast() == '/')
	{
		// this is an empty element tag, i.e. no contents or end tag (e.g <TAG attr="value" />
		parser->Expect('/');
		if (!parser->Expect('>'))
		{
			if (errorBuffer != nullptr)
				qtss_snprintf(errorBuffer, errorBufferSize, "'>' must follow '/' on line %d", parser->GetCurrentLineNumber());
			return false;   // bad attribute specification
		}

		return true;    // we're done with this tag
	}

	if (!parser->Expect('>'))
	{
		if (errorBuffer != nullptr)
			qtss_snprintf(errorBuffer, errorBufferSize, "Bad format for tag <%s> on line %d", fTag, parser->GetCurrentLineNumber());
		return false;   // bad attribute specification
	}

	while (true)
	{
		parser->ConsumeUntil(&temp, '<');   // this is either value or whitespace
		if (parser->GetDataRemaining() < 4)
		{
			if (errorBuffer != nullptr)
				qtss_snprintf(errorBuffer, errorBufferSize, "Reached end of file without end for tag <%s> declared on line %d", fTag, tagStartLine);
			return false;
		}
		if ((*parser)[1] == '/')
		{
			// we'll only assign a value if there were no embedded tags
			if (fEmbeddedTags.GetLength() == 0 && (!verifier || verifier->CanHaveValue(fTag)))
				fValue = temp.GetAsCString();
			else
			{
				// otherwise this needs to have been just whitespace
				StringParser tempParser(&temp);
				tempParser.ConsumeWhitespace();
				if (tempParser.GetDataRemaining() > 0)
				{
					if (errorBuffer)
					{
						if (fEmbeddedTags.GetLength() > 0)
							qtss_snprintf(errorBuffer, errorBufferSize, "Unexpected text outside of tag on line %d", tagStartLine);
						else
							qtss_snprintf(errorBuffer, errorBufferSize, "Tag <%s> on line %d not allowed to have data", fTag, tagStartLine);
					}
				}
			}
			break;  // we're all done with this tag
		}

		if (((*parser)[1] != '!') && ((*parser)[1] != '?'))
		{
			// this must be the beginning of an embedded tag
			XMLTag* tag = new XMLTag();
			fEmbeddedTags.EnQueue(&tag->fElem);
			if (!tag->ParseTag(parser, verifier, errorBuffer, errorBufferSize))
				return false;

			if (verifier && !verifier->IsValidSubtag(fTag, tag->GetTagName()))
			{
				if (errorBuffer != nullptr)
					qtss_snprintf(errorBuffer, errorBufferSize, "Tag %s not allowed in tag %s on line %d", tag->GetTagName(), fTag, parser->GetCurrentLineNumber());
				return false;   // bad attribute specification
			}
		}
		else
		{
			parser->ConsumeLength(nullptr, 1); // skip '<'
			consumeIfComment(parser);
		}
	}

	parser->ConsumeLength(nullptr, 2); // skip '</'
	parser->ConsumeUntil(&temp, sNonNameMask);
	if (!temp.Equal(fTag))
	{
		char* newTag = temp.GetAsCString();
		if (errorBuffer != nullptr)
			qtss_snprintf(errorBuffer, errorBufferSize, "End tag </%s> on line %d doesn't match tag <%s> declared on line %d", newTag, parser->GetCurrentLineNumber(), fTag, tagStartLine);
		delete newTag;
		return false;   // bad attribute specification
	}

	if (!parser->GetThru(nullptr, '>'))
	{
		if (errorBuffer != nullptr)
			qtss_snprintf(errorBuffer, errorBufferSize, "Couldn't find end of tag <%s> declared on line %d", fTag, tagStartLine);
		return false;   // bad attribute specification
	}

	return true;
}

char* XMLTag::GetAttributeValue(const char* attrName)
{
	for (OSQueueIter iter(&fAttributes); !iter.IsDone(); iter.Next())
	{
		XMLAttribute* attr = (XMLAttribute*)iter.GetCurrent()->GetEnclosingObject();
		if (!strcmp(attr->fAttrName, attrName))
			return attr->fAttrValue;
	}

	return nullptr;
}

XMLTag* XMLTag::GetEmbeddedTag(const UInt32 index)
{
	if (fEmbeddedTags.GetLength() <= index)
		return nullptr;

	OSQueueIter iter(&fEmbeddedTags);
	for (UInt32 i = 0; i < index; i++)
	{
		iter.Next();
	}
	OSQueueElem* result = iter.GetCurrent();

	return (XMLTag*)result->GetEnclosingObject();
}

XMLTag* XMLTag::GetEmbeddedTagByName(const char* tagName, const UInt32 index)
{
	if (fEmbeddedTags.GetLength() <= index)
		return nullptr;

	XMLTag* result = nullptr;
	UInt32 curIndex = 0;
	for (OSQueueIter iter(&fEmbeddedTags); !iter.IsDone(); iter.Next())
	{
		XMLTag* temp = (XMLTag*)iter.GetCurrent()->GetEnclosingObject();
		if (!strcmp(temp->GetTagName(), tagName))
		{
			if (curIndex == index)
			{
				result = temp;
				break;
			}

			curIndex++;
		}
	}

	return result;
}

XMLTag* XMLTag::GetEmbeddedTagByAttr(const char* attrName, const char* attrValue, const UInt32 index)
{
	if (fEmbeddedTags.GetLength() <= index)
		return nullptr;

	XMLTag* result = nullptr;
	UInt32 curIndex = 0;
	for (OSQueueIter iter(&fEmbeddedTags); !iter.IsDone(); iter.Next())
	{
		XMLTag* temp = (XMLTag*)iter.GetCurrent()->GetEnclosingObject();
		if ((temp->GetAttributeValue(attrName) != nullptr) && (!strcmp(temp->GetAttributeValue(attrName), attrValue)))
		{
			if (curIndex == index)
			{
				result = temp;
				break;
			}

			curIndex++;
		}
	}

	return result;
}

XMLTag* XMLTag::GetEmbeddedTagByNameAndAttr(const char* tagName, const char* attrName, const char* attrValue, const UInt32 index)
{
	if (fEmbeddedTags.GetLength() <= index)
		return nullptr;

	XMLTag* result = nullptr;
	UInt32 curIndex = 0;
	for (OSQueueIter iter(&fEmbeddedTags); !iter.IsDone(); iter.Next())
	{
		XMLTag* temp = (XMLTag*)iter.GetCurrent()->GetEnclosingObject();
		if (!strcmp(temp->GetTagName(), tagName) && (temp->GetAttributeValue(attrName) != nullptr) &&
			(!strcmp(temp->GetAttributeValue(attrName), attrValue)))
		{
			if (curIndex == index)
			{
				result = temp;
				break;
			}

			curIndex++;
		}
	}

	return result;
}

void XMLTag::AddAttribute(char* attrName, char* attrValue)
{
	XMLAttribute* attr = new XMLAttribute;
	StrPtrLen temp(attrName);
	attr->fAttrName = temp.GetAsCString();
	temp.Set(attrValue);
	attr->fAttrValue = temp.GetAsCString();

	fAttributes.EnQueue(&attr->fElem);
}

void XMLTag::RemoveAttribute(char* attrName)
{
	for (OSQueueIter iter(&fAttributes); !iter.IsDone(); iter.Next())
	{
		XMLAttribute* attr = (XMLAttribute*)iter.GetCurrent()->GetEnclosingObject();
		if (!strcmp(attr->fAttrName, attrName))
		{
			fAttributes.Remove(&attr->fElem);
			delete attr;
			return;
		}
	}
}

void XMLTag::AddEmbeddedTag(XMLTag* tag)
{
	fEmbeddedTags.EnQueue(&tag->fElem);
}

void XMLTag::RemoveEmbeddedTag(XMLTag* tag)
{
	fEmbeddedTags.Remove(&tag->fElem);
}

void XMLTag::SetTagName(char* name)
{
	Assert(name != nullptr);  // can't have a tag without a name!

	if (fTag != nullptr)
		delete fTag;

	StrPtrLen temp(name);
	fTag = temp.GetAsCString();
}

void XMLTag::SetValue(char* value)
{
	if (fEmbeddedTags.GetLength() > 0)
		return;     // can't have a value with embedded tags

	if (fValue != nullptr)
		delete fValue;

	if (value == nullptr)
		fValue = nullptr;
	else
	{
		StrPtrLen temp(value);
		fValue = temp.GetAsCString();
	}
}

void XMLTag::FormatData(ResizeableStringFormatter* formatter, UInt32 indent)
{
	for (UInt32 i = 0; i < indent; i++) formatter->PutChar('\t');

	formatter->PutChar('<');
	formatter->Put(fTag);
	if (fAttributes.GetLength() > 0)
	{
		formatter->PutChar(' ');
		for (OSQueueIter iter(&fAttributes); !iter.IsDone(); iter.Next())
		{
			XMLAttribute* attr = (XMLAttribute*)iter.GetCurrent()->GetEnclosingObject();
			formatter->Put(attr->fAttrName);
			formatter->Put("=\"");
			formatter->Put(attr->fAttrValue);
			formatter->Put("\" ");
		}
	}
	formatter->PutChar('>');

	if (fEmbeddedTags.GetLength() == 0)
	{
		if (fValue > 0)
			formatter->Put(fValue);
	}
	else
	{
		formatter->Put(kEOLString);
		for (OSQueueIter iter(&fEmbeddedTags); !iter.IsDone(); iter.Next())
		{
			XMLTag* current = (XMLTag*)iter.GetCurrent()->GetEnclosingObject();
			current->FormatData(formatter, indent + 1);
		}

		for (UInt32 i = 0; i < indent; i++) formatter->PutChar('\t');
	}

	formatter->Put("</");
	formatter->Put(fTag);
	formatter->PutChar('>');
	formatter->Put(kEOLString);
}

XMLAttribute::XMLAttribute()
	: fAttrName(nullptr),
	fAttrValue(nullptr)
{
	fElem = this;
}

XMLAttribute::~XMLAttribute()
{
	if (fAttrName)
		delete fAttrName;
	if (fAttrValue)
		delete fAttrValue;
}
